/*
 * $Id: cond.c,v 1.6 2006-02-07 08:04:12 bacon Exp $
 */

#include <xp/bas/cond.h>
#include <xp/bas/memory.h>
#include <xp/bas/assert.h>
#include <xp/bas/errno.h>

#if defined(_WIN32)
	#include <process.h>
#else
	#if defined(AIX) && defined(__GNUC__)
		typedef int crid_t;
		typedef unsigned int class_id_t;
	#endif
	#include <pthread.h>
#endif

xp_cond_t* xp_cond_open (xp_cond_t* cond)
{
	if (cond == XP_NULL) {
		cond = (xp_cond_t*) xp_malloc (xp_sizeof(xp_cond_t));
		if (cond == XP_NULL) return XP_NULL;
		cond->__dynamic = xp_true;
	}
	else cond->__dynamic = xp_false;

#ifdef _WIN32
	cond->gone    = 0;
	cond->blocked = 0;
	cond->waiting = 0;

	cond->gate = CreateSemaphore (0, 1, 1, 0);
	cond->queue = CreateSemaphore (0, 0, XP_TYPE_MAX(long), 0);
	cond->mutex = CreateMutex (0, 0, 0);

	if (cond->gate == XP_NULL || 
	    cond->queue == XP_NULL || 
	    cond->mutex == XP_NULL) {
		int num = xp_maperrno (GetLastError());

		if (cond->gate != XP_NULL) CloseHandle (cond->gate);
		if (cond->queue != XP_NULL) CloseHandle (cond->queue);
		if (cond->mutex != XP_NULL) CloseHandle (cond->mutex);
		if (cond->__dynamic) xp_free (cond);

		xp_seterrno (num);
		return XP_NULL;
	}
#else
	if (pthread_cond_init (&cond->hnd, XP_NULL) != 0) {
		int num = xp_geterrno();

		if (cond->__dynamic) xp_free (cond);

		xp_seterrno (num);
		return XP_NULL;
	}
#endif

	return cond;
}

void xp_cond_close (xp_cond_t* cond)
{
#ifdef _WIN32
	CloseHandle (cond->gate);
	CloseHandle (cond->queue);
	CloseHandle (cond->mutex);
#else
	pthread_cond_destroy (&cond->hnd);
#endif
	if (cond->__dynamic) xp_free (cond);
}

void xp_cond_signal (xp_cond_t* cond)
{
#ifdef _WIN32
	unsigned int signals = 0;

	WaitForSingleObject ((HANDLE)cond->mutex, INFINITE);
	if (cond->waiting != 0) {
		if (cond->blocked == 0) {
			ReleaseMutex ((HANDLE)cond->mutex);
			return;
		}

		++cond->waiting;
		--cond->blocked;
		signals = 1;
	}
	else {
		WaitForSingleObject ((HANDLE)cond->gate, INFINITE);
		if (cond->blocked > cond->gone) {
			if (cond->gone != 0) {
				cond->blocked -= cond->gone;
				cond->gone = 0;
			}
			signals = cond->waiting = 1;
			--cond->blocked;
		}
		else {
			ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
		}
	}

	ReleaseMutex ((HANDLE)cond->mutex);
	if (signals) ReleaseSemaphore ((HANDLE)cond->queue, signals, XP_NULL);
#else
	pthread_cond_signal (&cond->hnd);
#endif
}

void xp_cond_broadcast (xp_cond_t* cond)
{
#ifdef _WIN32
	unsigned int signals = 0;

	WaitForSingleObject ((HANDLE)cond->mutex, INFINITE);

	if (cond->waiting != 0) {
		if (cond->blocked == 0) {
			ReleaseMutex ((HANDLE)cond->mutex);
			return;
		}

		cond->waiting += (signals = cond->blocked);
		cond->blocked = 0;
	}
	else {
		WaitForSingleObject ((HANDLE)cond->gate, INFINITE);
		if (cond->blocked > cond->gone) {
			if (cond->gone != 0) {
				cond->blocked -= cond->gone;
				cond->gone = 0;
			}
			signals = cond->waiting = cond->blocked;
			cond->blocked = 0;
		}
		else {
			ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
		}
	}

	ReleaseMutex ((HANDLE)cond->mutex);
	if (signals) ReleaseSemaphore ((HANDLE)cond->queue, signals, XP_NULL);
#else
	pthread_cond_broadcast (&cond->hnd);
#endif
}

void xp_cond_wait (xp_cond_t* cond, xp_mtx_t* mutex)
{
#ifdef _WIN32
	unsigned int was_waiting, was_gone;

	WaitForSingleObject ((HANDLE)cond->gate, INFINITE);
	++cond->blocked;
	ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);

	xp_mtx_unlock (mutex);

	WaitForSingleObject ((HANDLE)cond->queue, INFINITE);
	
	was_waiting = 0; 
	was_gone = 0;

	WaitForSingleObject ((HANDLE)cond->mutex, INFINITE);

	was_waiting = cond->waiting;
	was_gone = cond->gone;

	if (was_waiting != 0) {
		if (--cond->waiting == 0) {
			if (cond->blocked != 0) {
				ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
				was_waiting = 0;
			}
			else if (cond->gone != 0) {
				cond->gone = 0;
			}
		}
	}
	else if (++cond->gone == XP_TYPE_MAX(unsigned int) / 2) {
		WaitForSingleObject ((HANDLE)cond->gate, INFINITE);
		cond->blocked -= cond->gone;
		ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
		cond->gone = 0;
	}

	ReleaseMutex ((HANDLE)cond->mutex);

	if (was_waiting == 1) {
		for (;was_gone; --was_gone) {
			WaitForSingleObject ((HANDLE)cond->queue, INFINITE);
		}
		ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
	}

	xp_mtx_lock (mutex);
#else
	pthread_cond_wait (&cond->hnd, &mutex->hnd);
#endif
}

void xp_cond_twait (
	xp_cond_t* cond, xp_mtx_t* mutex, xp_time_t waiting_time)
{
#ifdef _WIN32
	xp_bool_t signaled;
	unsigned int was_waiting, was_gone;

	WaitForSingleObject ((HANDLE)cond->gate, INFINITE);
	++cond->blocked;
	ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);

	xp_mtx_unlock (mutex);

	signaled = (WaitForSingleObject((HANDLE)cond->queue, waiting_time) == WAIT_OBJECT_0);

	was_waiting = 0; 
	was_gone = 0;

	WaitForSingleObject ((HANDLE)cond->mutex, INFINITE);

	was_waiting = cond->waiting;
	was_gone = cond->gone;

	if (was_waiting != 0) {
		if (!signaled) { /* timed out */
			if (cond->blocked != 0) --(cond->blocked);
			else ++(cond->gone);
		}
		if (--(cond->waiting) == 0) {
			if (cond->blocked != 0) {
				ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
				was_waiting = 0;
			}
			else if (cond->gone != 0) cond->gone = 0;
		}
	}
	else if (++(cond->gone) == XP_TYPE_MAX(unsigned int) / 2) {
		WaitForSingleObject ((HANDLE)cond->gate, INFINITE);
		cond->blocked -= cond->gone;
		ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
		cond->gone = 0;
	}

	ReleaseMutex ((HANDLE)cond->mutex);

	if (was_waiting == 1) {
		for (;was_gone; --was_gone) {
			WaitForSingleObject ((HANDLE)cond->queue, INFINITE);
		}
		ReleaseSemaphore ((HANDLE)cond->gate, 1, XP_NULL);
	}

	xp_mtx_lock (mutex);
#else
	xp_time_t now;
	xp_timespec_t timeout;

	xp_gettime (&now);
	now += waiting_time;

	XP_TIME_TO_TIMESPEC (now, &timeout);

	xp_assert (xp_sizeof(timeout) == xp_sizeof(struct timespec));
	pthread_cond_timedwait (&cond->hnd, &mutex->hnd, &timeout);
#endif
}

